
package GestionTablas;

import ConexionesBD.conexionBD;
import Esquemas.Atributo;
import Esquemas.Entidad;
import Esquemas.EntidadDebil;
import Esquemas.Esquema;
import Esquemas.RelacionEspecificacion;
import Esquemas.Participa;
import Esquemas.TipodeDato;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.logging.Logger;


/**
 * Clase encargada de generar el script (I/O, manejo de ficheros etc...) para crear la BD
 * @author Carmen M. Morillo Arias, Christian Neria Suárez, Diego Ojeda García
 */
public class CreaScript {
    
    ArrayList<Tabla> tablas;
    static Esquema e;
    
    /**
     * Constructor de la clase
     * @param tablas ArrayList de tablas
     * @param e Nombre de esquema
     */
    public CreaScript (ArrayList<Tabla> tablas, Esquema e)
    {
        this.tablas = tablas;
        CreaScript.e = e;
    }
    
    /**
     * Método encargado de crear el script e introducir contenido de creación de tablas
     */
    public void CrearScript () {
        FileWriter fichero = null;
        try {
            fichero = new FileWriter(e.getNombre() + ".sql");
            PrintWriter pw = new PrintWriter(fichero);
            pw.println("-- Borrado de tablas");
            for (int i=0; i < tablas.size(); i++)
            {
                if (e.getTiposdedatos().get(i) instanceof Entidad)
                {
                    pw.println("DROP TABLE " + tablas.get(i).getNombre() + " CASCADE CONSTRAINTS;");
                }
            }
            pw.println(" \n -- Creación de tablas");
            for (int i=0; i < tablas.size(); i++)
            {
                if (!tablas.get(i).getAtributos().isEmpty())
                {
                    if (e.getTiposdedatos().get(i) instanceof Entidad)
                    {
                        pw.println("CREATE TABLE " + tablas.get(i).getNombre() + " (");
                        for (int j=0; j < tablas.get(i).getAtributos().size(); j++)
                        {
                            if(!tablas.get(i).getAtributos().get(j).isMultievaluado() && tablas.get(i).getAtributos().get(j).getNombre_atributo_compuesto() == null)
                            {
                                if (j == (tablas.get(i).getAtributos().size()-1))
                                {
                                    pw.println("\t" + tablas.get(i).getAtributos().get(j).getNombre() + " " + tablas.get(i).getAtributos().get(j).getTipo());
                                    pw.println("); \n");
                                }
                                else
                                {
                                    pw.println("\t" + tablas.get(i).getAtributos().get(j).getNombre() + " " + tablas.get(i).getAtributos().get(j).getTipo() + ",");
                                }                                                                               
                            }                        
                        }
                    }  
               }
            }
            
        } catch (IOException ex) {
            Logger.getLogger(CreaScript.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            try {
                if (fichero != null)
                    fichero.close();
            } catch (IOException ex) {
                Logger.getLogger(CreaScript.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
    
    /**
     * Método para añadir restricciones a una entidad
     * @param esquema
     * @throws SQLException 
     */
    public void anadirRestriccionesEntidadFuerte(Esquema esquema) throws SQLException{
        LinkedList<TipodeDato> td = esquema.getTiposdedatos();
        
        conexionBD cbd = new conexionBD();
        cbd.conectar();
        
        Restriccion r = new Restriccion();                
        
        Entidad entidad;        
        for(int i=0;i<td.size();i++){
            if(td.get(i) instanceof Entidad){
                if(!(td.get(i) instanceof EntidadDebil)){                                                            
                    entidad = (Entidad) td.get(i);                    
                    
                    int nrelaciones = cbd.obtenerEntidadAislada(entidad, esquema);                    
                    if(nrelaciones > 0){ //Si no es una entidad sin relaciones, aislada
                        cbd.obtenerClaveEntidad(entidad, esquema);
                        if(!entidad.getClave().isEmpty()){ //Si tiene clave
                            ArrayList<Atributo> claves = entidad.getClave();  
                            ArrayList<Atributo> aux = new ArrayList<Atributo>(); 
                            ArrayList<Atributo> aux2 = new ArrayList<Atributo>(); 
                            for(Atributo a : claves){
                                if(a.isMultievaluado()){ //Si la clave es multievaluado meto sus atributos como clave                                    
                                    marcarMultievaluado(a, entidad);
                                    LinkedList<Atributo> multiclave = this.crearTablasAtributoCompuesto(a, entidad);                                    
                                    for(Atributo c : multiclave){
                                        aux.add(c);
                                        r.anadirAtributoTabla(entidad, c);
                                    } 
                                    aux2.add(a);
                                }
                                else{
                                    aux.add(a);
                                }
                            }
                            entidad.borrarClaves();
                            for(Atributo a : aux){
                                entidad.setClave(a);
                            }
                            
                            r.anadirRestriccionClavePrimaria(entidad);                                    
                        }                                                
                    }                        
                }
            }
        }                
    }
    
    public void marcarMultievaluado(Atributo a, Entidad e){
        for(Atributo at : e.getAtributos()){
            if(at instanceof Atributo){
                if(at.getNombre().equalsIgnoreCase(a.getNombre())){
                    at.setFueClave(true);
                }
            }
        }
    }
    
    /**
     * Método para añadir restricciones a una entidad débil
     * @param esquema
     * @throws SQLException 
     */
    public void anadirRestriccionesEntidadDebil(Esquema esquema) throws SQLException{
        LinkedList<TipodeDato> td = esquema.getTiposdedatos();
        
        conexionBD cbd = new conexionBD();
        cbd.conectar();
        
        Restriccion r = new Restriccion();
        
        Entidad entidadFuerte;
        EntidadDebil entidaddebil;
        for(int i=0;i<td.size();i++){            
            if(td.get(i) instanceof EntidadDebil){
                entidaddebil= (EntidadDebil) td.get(i);                                   
                
                //Obtenemos su clave parcial
                cbd.obtenerClaveParcialEntidadDebil(entidaddebil, esquema);

                //Obtenemos todas las relaciones de de especificación en las que participa
                ArrayList<RelacionEspecificacion> listaRelacionesEsp = this.obtenerRelacionesEspecificacionEntidadDebil(entidaddebil, e);
                
                //Obtenemos todas las entidades fuertes que participan en cada relación de especificación
                for(int k=0;k<listaRelacionesEsp.size();k++){                                                            
                    ArrayList<String> entidadesFuertes = cbd.obtenerClavesEntidadDebil(entidaddebil, esquema, listaRelacionesEsp.get(k));
                    
                    //Para cada una de esas entidades fuertes, obtenemos sus claves y añadimos los atributos y restricciones de clave foránea
                    for(int j=0;j<entidadesFuertes.size();j++){                                                
                        entidadFuerte = esquema.obtenerEntidadFuerte(entidadesFuertes.get(j));
                        
                        if(!(entidadFuerte instanceof EntidadDebil)){ //Si es una entidad fuerte                            
                            for(int h=0;h<entidadFuerte.getClave().size();h++){                            
                                entidaddebil.setClave(entidadFuerte.getClave().get(h));                                    
                            }
                            r.anadirAtributosTabla(entidadFuerte, entidaddebil);
                            r.anadirRestriccionesClaveForanea(entidadFuerte,entidaddebil);                                                                                    
                        }
                        else { //Si se relaciona con una entidad débil hay que mirar las cardinalidades                          
                            String maxmied = cbd.obtenerCardinalidad(entidaddebil, esquema, listaRelacionesEsp.get(k));
                            String maxed = cbd.obtenerCardinalidad(entidadFuerte, esquema, listaRelacionesEsp.get(k));                            
                            
                            if(maxmied != null){
                                if(maxmied.equalsIgnoreCase("1") && maxed.equalsIgnoreCase("N")){                                                                        
                                    for(int h=0;h<entidadFuerte.getClave().size();h++){                            
                                        entidaddebil.setClave(entidadFuerte.getClave().get(h));                                 
                                    }
                                    r.anadirAtributosTabla(entidadFuerte, entidaddebil);
                                    r.anadirRestriccionesClaveForanea(entidadFuerte,entidaddebil);                                                                                                 
                                }                            
                            }

                        
                        }
                    }                                                            
                }
                
                //Añadimos la restricción de clave primaria a la entidad débil
                if(entidaddebil.getClaveParcial() != null){ //En caso de que la entidad no tenga clave parcial
                    entidaddebil.setClave(entidaddebil.getClaveParcial());                    
                }                
                r.anadirRestriccionClavePrimaria(entidaddebil);
            }            
        }                
        
                
    
    }
    

    /**
     * Obtiene todas las relaciones de especificación en la que participa una entidad débil
     * @param ed Entidad débil
     * @param e Esquema
     * @return ArrayList con todas las relaciones de especificación
     * @throws SQLException 
     */
    private ArrayList<RelacionEspecificacion> obtenerRelacionesEspecificacionEntidadDebil(EntidadDebil ed, Esquema e) throws SQLException{
        conexionBD cbd = new conexionBD();        
        cbd.conectar();
        
        ArrayList<String> relacionesEspecificacion = cbd.obtenerRelacionesEspecificacionEntidadDebil(ed, e);
        ArrayList<RelacionEspecificacion> relacionesEspEntidad = new ArrayList<RelacionEspecificacion>();
        relacionesEspEntidad.clear();                
        
        for(int i=0;i<e.getTiposdedatos().size();i++){//Recorro los tipos de datos buscando las relaciones de especificacion para esa entidad debil
            
            if(e.getTiposdedatos().get(i) instanceof RelacionEspecificacion){               
               
                for(int j=0;j<relacionesEspecificacion.size();j++){ //Busco si esa relación está en la lista de relaciones de la entidad débil
                   
                  RelacionEspecificacion respecif = (RelacionEspecificacion) e.getTiposdedatos().get(i);
                  if(respecif.getNombre().equalsIgnoreCase(relacionesEspecificacion.get(j))) {
                      relacionesEspEntidad.add(respecif);                       
                      break;
                  }                     
                }                              
            }
        }                         
        return relacionesEspEntidad;
    }        
    
    /**
     * Añade las vistas para el cálculo de atributos derivados
     * @param e Esquema
     */
    public void anadirVistaAtributosDerivados(Esquema e){
        LinkedList<TipodeDato> tds = e.getTiposdedatos();
        LinkedList<Atributo> atributos;
        LinkedList<Atributo> atributosmultievaluados;        
        
        Restriccion r = new Restriccion();
        for(int j=0;j<tds.size();j++){
            if(tds.get(j) instanceof Entidad){
                atributos = tds.get(j).getAtributos();
                for(int i=0;i<atributos.size();i++){
                    if(atributos.get(i).isDerivado()){
                        r.crearVistaAtributoDerivado(atributos.get(i));
                    }
                    else if(atributos.get(i).isMultievaluado() && atributos.get(i).isFueClave()==false){                        
                        atributosmultievaluados = this.crearTablasAtributoCompuesto(atributos.get(i),(Entidad) tds.get(j));                        
                        r.crearTablaAtributoMultievaluado(atributos.get(i), atributosmultievaluados,(Entidad) tds.get(j));
                    }
                }                
            }
        }                                
    }

    /**
     * Obtener todos los atributos que forman parte del atributo multievaluado
     * @param atributo Atributo multievaluado
     * @param entidad Entidad a la que pertenece el atributo
     * @return LinkedList con todos los atributos que forman parte del compuesto
     */
    public LinkedList<Atributo> crearTablasAtributoCompuesto(Atributo atributo, Entidad entidad){
        LinkedList<Atributo> atributos = entidad.getAtributos();
        LinkedList<Atributo> atributosmultievaluados = new LinkedList<Atributo>();
                        
        for(Atributo a : atributos){            
            if(a.getNombre_atributo_compuesto() != null && a.getNombre_atributo_compuesto().equalsIgnoreCase(atributo.getNombre())){
                
                atributosmultievaluados.add(a);
            }
        }  
        
        return atributosmultievaluados;
    }
                        
    /**
     * Añade las participaciones - No terminado.
     * @param esquema Esquema 
     * @throws SQLException 
     */
    public void anadirParticipaciones(Esquema esquema) throws SQLException {
        ArrayList<Participa> p;
        ArrayList<Participa> currentP;
        boolean encontrado;
        FileWriter fichero = null;
        ArrayList<String> relaciones = new ArrayList<String>();
        conexionBD cbd = new conexionBD();
        cbd.conectar();
        p = cbd.obtenerParticipaciones(esquema);
        for (Participa pa : p){
            currentP = new ArrayList<Participa>();
            encontrado = false;
            for (int i = 0; i<relaciones.size(); i++){
                if (relaciones.get(i).equals(pa.getRelacion())){
                    encontrado = true;
                }
            }
            if (!encontrado){
                relaciones.add(pa.getRelacion());
                for (Participa pAux : p){
                    if (pa.getRelacion().equals(pAux.getRelacion())){
                        currentP.add(pAux);
                    }
                }
                //En currentP en este momento tenemos la relación con la que estamos trabajando con todos sus papeles
                if (currentP.size() > 2){
                    //Crear nueva tabla para una relación n-aria con n>2
                }
                else{
                    //Distinguir si N-M, 1-N, o 1-1
                    if ("N".equals(currentP.get(0).getCardinalidadMaxima()) && "N".equals(currentP.get(1).getCardinalidadMaxima())){
                        //Relación N-M. Crear nueva tabla
                        try {
                            fichero = new FileWriter(e.getNombre() + ".sql",true);
                            PrintWriter pw = new PrintWriter(fichero);
                            pw.append("--Anadir tabla para la relacion: " + currentP.get(0).getRelacion() + "\n");
                            pw.append("CREATE TABLE " +currentP.get(0).getRelacion()+ " (");
                            
                        } catch (IOException ex) {
                            Logger.getLogger(CreaScript.class.getName()).log(Level.SEVERE, null, ex);
                        }
                        finally{
                            try {
                                if (fichero != null)
                                    fichero.close();
                            } catch (IOException ex) {
                                Logger.getLogger(CreaScript.class.getName()).log(Level.SEVERE, null, ex);
                            }
                        }
                        
                    }
                    else if ("N".equals(currentP.get(0).getCardinalidadMaxima()) || "N".equals(currentP.get(1).getCardinalidadMaxima())){
                        //Relación uno a muchos. Añadir foreign key a la parte de 1
                    }
                    else{
                        //Relación 1-1. Ver cardinalidaides mínimas
                    }
                }
            }
        }
    }
    
    public static Esquema getEsquema (){
        return e;
    }
}
